package cn.wolfcode.service.impl;

import cn.wolfcode.common.exception.BusinessException;
import cn.wolfcode.web.msg.SeckillCodeMsg;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class DistributedLockSupport {

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RedisScript<Boolean> lockScript;
    @Autowired
    private RedisScript<Boolean> unlockScript;
    @Autowired
    private ScheduledThreadPoolExecutor scheduledThreadPoolExecutor;

    public void lock(String lockKey, int timeout, BusinessHandler handler) {
        // 创建一个线程 id，最终存入锁中，释放时去除线程id，判断要释放的锁还是不是当前的线程id，如果是，才释放
        String threadId = UUID.randomUUID().toString();
        ScheduledFuture<?> future = null;
        try {
            boolean locked = false;
            int count = 0, max = 5;
            do {
                // 判断如果失败次数达到5次就直接结束，抛出异常
                if (count >= max) {
                    throw new BusinessException(SeckillCodeMsg.SECKILL_ERROR);
                }

                // 先尝试加锁，看能否添加成功
                // 1. 增加过期时间，避免加锁后因为意外宕机，导致锁不能被释放
                locked = Boolean.TRUE.equals(redisTemplate.execute(lockScript, Collections.singletonList(lockKey), threadId, timeout + ""));
                if (locked) {
                    // 如果加锁成功，直接返回结果
                    break;
                }

                // 如果加锁失败，睡100毫秒，失败数量+1
                TimeUnit.MILLISECONDS.sleep(100);
                count++;
            } while (true); // 自旋锁

            // 创建 WatchDog
            int delay = timeout / 2 + 1;
            future = scheduledThreadPoolExecutor.scheduleAtFixedRate(() -> {
                // 判断是否已经执行完毕
                String value = redisTemplate.opsForValue().get(lockKey);
                // 判断是否有 value
                if (!StringUtils.isEmpty(value)) {
                    // 对 key 进行续期
                    redisTemplate.expire(lockKey, delay + 2, TimeUnit.SECONDS);
                }
            }, delay, delay, TimeUnit.SECONDS);

            // 业务逻辑
            handler.handle();
        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            log.error("[分布式锁] 加锁失败：{}", e.getMessage());
            throw new BusinessException(SeckillCodeMsg.SECKILL_ERROR);
        } finally {
            // 如果业务正常执行结束，就释放锁，并取消 WatchDog
            if (future != null) {
                future.cancel(true);
            }
            redisTemplate.execute(unlockScript, Collections.singletonList(lockKey), threadId);
        }
    }

    @FunctionalInterface
    public static interface BusinessHandler {

        void handle();
    }
}
